﻿using System;
using System.Linq;
using System.Linq.Expressions;
using System.Net;
using System.Reflection;
using Raven.Client;
using Raven.Client.Document;
using Raven.Client.Indexes;
using Raven.Client.Linq;
using System.Collections.Generic;

namespace Crawler.DataAccess
{
    public class RavenDBDocumentRepository : IDisposable
    {
        private readonly IDocumentStore m_DocumentStore;
        private readonly string m_Database;
        private bool m_Disposed;
        private IDocumentSession m_DocumentSession;
        private static ICredentials s_DatabaseCredential = null;

        private IDocumentSession DocumentSession
        {
            get
            {
                if (m_DocumentSession == null)
                {
                    if (string.IsNullOrEmpty(m_Database))
                    {
                        string a = m_DocumentStore.Identifier;
                        m_DocumentSession = m_DocumentStore.OpenSession();
                    }
                    else
                    {
                        m_DocumentSession = m_DocumentStore.OpenSession(m_Database);
                    }
                }

                return m_DocumentSession;
            }
        }


        protected static ICredentials DatabaseCredential
        {
            get
            {
                if (s_DatabaseCredential == null)
                {
                    s_DatabaseCredential = new NetworkCredential("", "", "");
                }
                return s_DatabaseCredential;
            }
        }

        public RavenDBDocumentRepository(string documentUrl)
            : this(documentUrl, RavenDBDocumentRepository.DatabaseCredential)
        {
        }

        public RavenDBDocumentRepository(string documentUrl, string database)
            : this(documentUrl, database, RavenDBDocumentRepository.DatabaseCredential)
        {
        }

        public RavenDBDocumentRepository(string documentUrl, ICredentials credentials)
            : this(documentUrl, null, RavenDBDocumentRepository.DatabaseCredential)
        {
        }

        public RavenDBDocumentRepository(string documentUrl, string database, ICredentials credentials)
        {
            m_Database = database;
            m_DocumentStore = RavenDBDocumentStoreFactory.GetDocumentStore(documentUrl, credentials);
            m_DocumentStore.Conventions.MaxNumberOfRequestsPerSession = 1000;
        }

        public static void RegisterDocumentStoreIndexAssembly(string documentUrl, Assembly assembly)
        {
            RavenDBDocumentStoreFactory.RegisterDocumentStoreIndexAssembly(documentUrl, assembly);
        }

        /// <summary>
        ///  Marks the specified entity for deletion. The entity will be deleted when
        ///  Cronom.Commerce.Providers.RavenDBRepository.SaveChanges() is called.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="entity"></param>
        public void Delete<T>(T entity)
        {
            DocumentSession.Delete<T>(entity);
        }

        public void DeleteAll<T>()
        {
            List<T> documents;
            do
            {
                documents = Query<T>().ToList();
                documents.ForEach(Delete);

                SaveChanges();
            } while (documents.Count > 0);
        }

        /// <summary>
        ///  Begin a load while including the specified path
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="path"></param>
        /// <returns></returns>
        public ILoaderWithInclude<T> Include<T>(Expression<Func<T, object>> path)
        {
            return DocumentSession.Include<T>(path);
        }

        /// <summary>
        /// Begin a load while including the specified path
        /// </summary>
        /// <param name="path"></param>
        /// <returns></returns>
        public ILoaderWithInclude<object> Include(string path)
        {
            return DocumentSession.Include(path);
        }

        /// <summary>
        ///  Loads the specified entities with the specified ids.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="documentName"></param>
        /// <param name="ids"></param>
        /// <returns></returns>
        public T[] Load<T>(string documentName, params string[] ids)
        {
            return DocumentSession.Load<T>(ids.Select(id => DocumentBase.GetDocumentID(documentName, id)).ToArray());
        }

        /// <summary>
        ///  Loads the specified entities with the specified ids.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="ids"></param>
        /// <returns></returns>
        public T[] Load<T>(params string[] ids)
        {
            return DocumentSession.Load<T>(ids);
        }

        /// <summary>
        /// Loads the specified entity with the specified id.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="id"></param>
        /// <returns></returns>
        public T Load<T>(string id)
        {
            return DocumentSession.Load<T>(id);
        }

        /// <summary>
        ///  Loads the specified entities with the specified ids.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="documentName"></param>
        /// <param name="id"></param>
        /// <returns></returns>
        public T Load<T>(string documentName, string id)
        {
            return DocumentSession.Load<T>(DocumentBase.GetDocumentID(documentName, id));
        }

        /// <summary>
        /// Dynamically queries RavenDB using LINQ
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <returns></returns>
        public IRavenQueryable<T> Query<T>()
        {
            return DocumentSession.Query<T>();
        }

        /// <summary>
        /// Queries the index specified by TIndexCreator using Linq.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <typeparam name="TIndexCreator"></typeparam>
        /// <returns></returns>
        public IRavenQueryable<T> Query<T, TIndexCreator>()
            where TIndexCreator : AbstractIndexCreationTask, new()
        {
            return DocumentSession.Query<T, TIndexCreator>();
        }

        /// <summary>
        /// Queries the specified index using Linq.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="indexName"></param>
        /// <returns></returns>
        public IRavenQueryable<T> Query<T>(string indexName)
        {
            return DocumentSession.Query<T>(indexName);
        }

        /// <summary>
        /// Dynamically query RavenDB using Lucene syntax
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <returns></returns>
        public IDocumentQuery<T> LuceneQuery<T>()
        {
            return DocumentSession.Advanced.LuceneQuery<T>();
        }

        /// <summary>
        /// Query the specified index using Lucene syntax
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="indexName"></param>
        /// <returns></returns>
        public IDocumentQuery<T> LuceneQuery<T>(string indexName)
        {
            return DocumentSession.Advanced.LuceneQuery<T>(indexName);
        }

        /// <summary>
        /// Dynamically query RavenDB using Lucene syntax
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <typeparam name="TIndexCreator"></typeparam>
        /// <returns></returns>
        public IDocumentQuery<T> LuceneQuery<T, TIndexCreator>()
            where TIndexCreator : AbstractIndexCreationTask, new()
        {
            return DocumentSession.Advanced.LuceneQuery<T, TIndexCreator>();
        }

        /// <summary>
        /// Saves all the changes to the Raven server.
        /// </summary>
        public void SaveChanges()
        {
            DocumentSession.SaveChanges();
        }

        /// <summary>
        /// Stores entity to the Raven server.
        /// </summary>
        /// <param name="entity"></param>
        public void Store(dynamic entity)
        {
            DocumentSession.Store(entity);
        }

        #region IDisposable Members

        protected virtual void Dispose(bool disposing)
        {
            if (!m_Disposed)
            {
                if (disposing)
                {
                    if (m_DocumentSession != null)
                    {
                        m_DocumentSession.Dispose();
                        m_DocumentSession = null;
                    }
                }

                m_Disposed = true;
            }
        }

        ~RavenDBDocumentRepository()
        {
            Dispose(false);
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        #endregion
    }
}
